#pragma once

#include "JsiDomDeclarationNode.h"

#include "NodeProp.h"

#include <memory>
#include <string>
#include <vector>

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdocumentation"

#include "include/core/SkPathEffect.h"

#pragma clang diagnostic pop

namespace RNSkia {
class JsiBasePathEffectNode : public JsiDomDeclarationNode {
public:
    JsiBasePathEffectNode(std::shared_ptr<RNSkPlatformContext> context, const char *type)
        : JsiDomDeclarationNode(context, type, DeclarationType::PathEffect)
    {}

protected:
    void composeAndPush(DeclarationContext *context, sk_sp<SkPathEffect> pe1)
    {
        context->save();
        decorateChildren(context);
        auto pe2 = context->getPathEffects()->popAsOne();
        context->restore();
        auto pe = pe2 ? SkPathEffect::MakeCompose(pe1, pe2) : pe1;
        context->getPathEffects()->push(pe);
    }
};

class JsiDashPathEffectNode : public JsiBasePathEffectNode, public JsiDomNodeCtor<JsiDashPathEffectNode> {
public:
    explicit JsiDashPathEffectNode(std::shared_ptr<RNSkPlatformContext> context)
        : JsiBasePathEffectNode(context, "skDashPathEffect")
    {}

    void decorate(DeclarationContext *context) override
    {
        // Phase
        auto phase = _phase->isSet() ? _phase->value().getAsNumber() : 0;

        // Copy intervals
        std::vector<SkScalar> intervals;
        auto intervalsArray = _intervals->value().getAsArray();
        for (size_t i = 0; i < intervalsArray.size(); ++i) {
            intervals.push_back(intervalsArray[i].getAsNumber());
        }

        // Create effect
        auto pathEffect = SkDashPathEffect::Make(intervals.data(), static_cast<int>(intervals.size()), phase);

        composeAndPush(context, pathEffect);
    }

protected:
    void defineProperties(NodePropsContainer *container) override
    {
        JsiDomDeclarationNode::defineProperties(container);

        _intervals = container->defineProperty<NodeProp>("intervals");
        _phase = container->defineProperty<NodeProp>("phase");

        _intervals->require();
    }

private:
    NodeProp *_intervals;
    NodeProp *_phase;
};

class JsiDiscretePathEffectNode : public JsiBasePathEffectNode, public JsiDomNodeCtor<JsiDiscretePathEffectNode> {
public:
    explicit JsiDiscretePathEffectNode(std::shared_ptr<RNSkPlatformContext> context)
        : JsiBasePathEffectNode(context, "skDiscretePathEffect")
    {}

    void decorate(DeclarationContext *context) override
    {
        // Create effect
        auto pathEffect = SkDiscretePathEffect::Make(_lengthProp->value().getAsNumber(),
            _deviationProp->value().getAsNumber(), _seedProp->value().getAsNumber());

        composeAndPush(context, pathEffect);
    }

protected:
    void defineProperties(NodePropsContainer *container) override
    {
        JsiDomDeclarationNode::defineProperties(container);

        _lengthProp = container->defineProperty<NodeProp>("length");
        _deviationProp = container->defineProperty<NodeProp>("deviation");
        _seedProp = container->defineProperty<NodeProp>("seed");

        _lengthProp->require();
        _deviationProp->require();
        _seedProp->require();
    }

private:
    NodeProp *_lengthProp;
    NodeProp *_deviationProp;
    NodeProp *_seedProp;
};

class JsiCornerPathEffectNode : public JsiBasePathEffectNode, public JsiDomNodeCtor<JsiCornerPathEffectNode> {
public:
    explicit JsiCornerPathEffectNode(std::shared_ptr<RNSkPlatformContext> context)
        : JsiBasePathEffectNode(context, "skCornerPathEffect")
    {}

    void decorate(DeclarationContext *context) override
    {
        // Create effect
        auto pathEffect = SkCornerPathEffect::Make(_rProp->value().getAsNumber());

        composeAndPush(context, pathEffect);
    }

protected:
    void defineProperties(NodePropsContainer *container) override
    {
        JsiDomDeclarationNode::defineProperties(container);

        _rProp = container->defineProperty<NodeProp>("r");
        _rProp->require();
    }

private:
    NodeProp *_rProp;
};

class JsiPath1DPathEffectNode : public JsiBasePathEffectNode, public JsiDomNodeCtor<JsiPath1DPathEffectNode> {
public:
    explicit JsiPath1DPathEffectNode(std::shared_ptr<RNSkPlatformContext> context)
        : JsiBasePathEffectNode(context, "skPath1DPathEffect")
    {}

    void decorate(DeclarationContext *context) override
    {
        // Create effect
        auto pathEffect = SkPath1DPathEffect::Make(*_pathProp->getDerivedValue(), _advanceProp->value().getAsNumber(),
            _phaseProp->value().getAsNumber(), getStyleFromStringValue(_styleProp->value().getAsString()));

        composeAndPush(context, pathEffect);
    }

protected:
    void defineProperties(NodePropsContainer *container) override
    {
        JsiDomDeclarationNode::defineProperties(container);

        _phaseProp = container->defineProperty<NodeProp>("phase");
        _advanceProp = container->defineProperty<NodeProp>("advance");
        _pathProp = container->defineProperty<PathProp>("path");
        _styleProp = container->defineProperty<NodeProp>("style");

        _phaseProp->require();
        _advanceProp->require();
        _pathProp->require();
        _styleProp->require();
    }

private:
    SkPath1DPathEffect::Style getStyleFromStringValue(const std::string &value)
    {
        if (value == "translate") {
            return SkPath1DPathEffect::kTranslate_Style;
        } else if (value == "rotate") {
            return SkPath1DPathEffect::kRotate_Style;
        } else if (value == "morph") {
            return SkPath1DPathEffect::kMorph_Style;
        }
        throw std::runtime_error("Value \"" + value + "\" is not a valid Path1D effect style.");
    }

    NodeProp *_phaseProp;
    NodeProp *_advanceProp;
    NodeProp *_styleProp;
    PathProp *_pathProp;
};

class JsiPath2DPathEffectNode : public JsiBasePathEffectNode, public JsiDomNodeCtor<JsiPath2DPathEffectNode> {
public:
    explicit JsiPath2DPathEffectNode(std::shared_ptr<RNSkPlatformContext> context)
        : JsiBasePathEffectNode(context, "skPath2DPathEffect")
    {}

protected:
    void decorate(DeclarationContext *context) override
    {
        // Create effect
        auto pathEffect = SkPath2DPathEffect::Make(*_matrixProp->getDerivedValue(), *_pathProp->getDerivedValue());

        composeAndPush(context, pathEffect);
    }

    void defineProperties(NodePropsContainer *container) override
    {
        JsiDomDeclarationNode::defineProperties(container);

        _matrixProp = container->defineProperty<MatrixProp>("matrix");
        _pathProp = container->defineProperty<PathProp>("path");

        _matrixProp->require();
        _pathProp->require();
    }

private:
    MatrixProp *_matrixProp;
    PathProp *_pathProp;
};

class JsiLine2DPathEffectNode : public JsiBasePathEffectNode, public JsiDomNodeCtor<JsiLine2DPathEffectNode> {
public:
    explicit JsiLine2DPathEffectNode(std::shared_ptr<RNSkPlatformContext> context)
        : JsiBasePathEffectNode(context, "skLine2DPathEffect")
    {}

protected:
    void decorate(DeclarationContext *context) override
    {
        // Create effect
        auto pathEffect = SkLine2DPathEffect::Make(_widthProp->value().getAsNumber(), *_matrixProp->getDerivedValue());

        composeAndPush(context, pathEffect);
    }

    void defineProperties(NodePropsContainer *container) override
    {
        JsiDomDeclarationNode::defineProperties(container);

        _matrixProp = container->defineProperty<MatrixProp>("matrix");
        _widthProp = container->defineProperty<NodeProp>("width");

        _matrixProp->require();
        _widthProp->require();
    }

private:
    MatrixProp *_matrixProp;
    NodeProp *_widthProp;
};

class JsiSumPathEffectNode : public JsiBasePathEffectNode, public JsiDomNodeCtor<JsiSumPathEffectNode> {
public:
    explicit JsiSumPathEffectNode(std::shared_ptr<RNSkPlatformContext> context)
        : JsiBasePathEffectNode(context, "skSumPathEffect")
    {}

protected:
    void decorate(DeclarationContext *context) override
    {
        decorateChildren(context);
        auto pe = context->getPathEffects()->Declaration<sk_sp<SkPathEffect>>::popAsOne(
            [=](sk_sp<SkPathEffect> inner, sk_sp<SkPathEffect> outer) { return SkPathEffect::MakeSum(inner, outer); });
        context->getPathEffects()->push(pe);
    }
};
} // namespace RNSkia
